package impl.comp;

import fr.irit.smac.may.lib.components.messaging.receiver.AgentRef;
import interfaces.comp.PlageHoraireIface;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import mysticedt.PlagesHoraires;
import reservation.JourHoraireType;
import reservation.PlageHoraire;
import contraintes.CreneauNonExploitable;

/**
 * Classe PlagesHorairesImpl.
 * La creation et l'utilisation d'une place horaire se fait dans cette classe.
 * @author Ben
 *
 */
public class PlagesHorairesImpl extends PlagesHoraires {
	
	/**
	 * Tableau des creneaux Non Exploitable de l'emploi du temps.
	 */
	private ArrayList<CreneauNonExploitable> listeCNE = new ArrayList<CreneauNonExploitable>();
	/**
	 * Matrice (tableau 2 dimensions) Dimension key: les dates demandees | Dimension valeur: liste des creneaux utilises pour cette date.
	 */
	private Hashtable<Date,JourHoraireType> listeJours = new Hashtable <Date,JourHoraireType>(); 
	/**
	 * Liste des plages horaires de l'emploi du temps : listePlagesHoraires.
	 */
	private Hashtable<Integer,PlageHoraire> listePlagesHoraires = new Hashtable <Integer,PlageHoraire>();
	/**
	 * Identifiant (id) des plages horaires crees.
	 */
	private int numeroCreneau = 0;
	
	
	/**
	 * Fonction public qui permet d'ajouter des contraintes pour l'elaboration des plages horaires.
	 * @param cne
	 */
	public void addConstraintInListeCNE(CreneauNonExploitable cne) {
		this.listeCNE.add(cne);
	}
	
	/**
	 * Fonction public qui permet de supprimer des contraintes pour l'elaboration des plages horaires.
	 * @param cne
	 */
	public void delConstraintInListeCNE(CreneauNonExploitable cne) {
		this.listeCNE.remove(cne);
	}
	
	/**
	 * Fonction public qui permet d'inialiser la liste de contraintes pour l'elaboration des plages horaires.
	 * @param listeCNE
	 */
	public void setListeCNE (ArrayList<CreneauNonExploitable> listeCNE){
		this.listeCNE = listeCNE;
	}
	
	/**
	 * Constructeur.
	 */
	@Override
	protected PlageHoraireIface plageHoraire() {
		return new PlageHoraireIface() {
			public PlageHoraire demanderCreneau(Date limite, int dure,
					AgentRef a) {
				return listePlagesHoraires.get(trouveHorairePossibleJour(limite, dure));
			}
			public boolean reserverCreneau(int idPh, AgentRef a) {
				if(listePlagesHoraires.get(idPh).getProprio()!=null)
					return false;
				listePlagesHoraires.get(idPh).setProprio(a);
				return true;
			}
		};

	}
	
	/**
	 * Fonction qui verifie si un jour donnee est dans la liste des CNE.
	 * @param d
	 * @return dateCE
	 */
	private Date verifieJourInListeCNE( Date d ) {
		// On verifie que la date demandee ne soit pas dans un creneau non exploitable
		while(listeCNE.contains(d))
			d.setTime(d.getTime()+24*60*1000*60);/*Sinon on rajoute une journee a la date */
		return d;
	}
	
	/**
	 * Fonction : A partir d'une date donnee et d'une duree donnee
	 * trouve la date de fin, ceci dans le but de creer une plage horaire.
	 * @param dateDebut
	 * @param duree
	 * @return dateDebut+duree
	 */
	private Date trouveDateFin(Date dateDebut, int duree) {
		/*
		 * dateDebut.getTime() etant en milliseconde, on rajoute la duree en milliseconde aussi 
		 */
		long dateFin = dateDebut.getTime()+ (long)duree*(1000*60);
		return new Date(dateFin);
	}
	
	// Retourne la plage horaire trouve (si elle existe) dans la date donnee
	private boolean donneHoraire(Date dateDemandee, int duree){
		
		/*
		 * Attribut trouver : si pour cette date donnee, il existe un creneau disponible avec la duree demandee 
		 */
		boolean trouver = false;
		/*
		 * Attribut listePlageHoraireDispo : qui recupere la liste des plages horaires disponibles pour cette date
		 */
		JourHoraireType listeHoraireJourDispo = listeJours.get(dateDemandee);
		/*
		 * Attribut heure_possible : stock l'heure de debut de la plage horaire, si
		 * celle-ci est trouvee, sinon stockera la valeur '-1' signifiant qu'elle n'a
		 * pas ete trouvee
		 */
		int heure_possible = listeHoraireJourDispo.existePlageHorairePossible(duree);
		@SuppressWarnings("deprecation")
		Date newDate = new Date(dateDemandee.getYear(),dateDemandee.getMonth(),dateDemandee.getDay());
		/* 
		 * Si on a trouver une plage horaire disponible, alors on la cree et 
		 * on l'ajoute a la liste des plages horaires de la date en question
		 */
		if(heure_possible != -1){
			/*
			 * On a trouve une plage horaire, on va donc l'enregistrer
			 */
			trouver = true;
			/* 
			 * On cree la nouvelle plage horaire, avec les informations prises precedemment
			 */
			Date date_deb = new Date(newDate.getTime() + heure_possible*(60*60*1000));
			Date date_fin = trouveDateFin(date_deb,duree);
			PlageHoraire plageHoraire = new PlageHoraire(this.numeroCreneau++, date_deb, date_fin);
			/*
			 * MAJ : On ajoute cette plage horaire dans la liste de nos plages horaires creees
			 */
			this.listePlagesHoraires.put(this.numeroCreneau, plageHoraire);
			/*
			 * MAJ : On enleve les creneaux possibles pour cette journee
			 */
			listeHoraireJourDispo.enleveCreneauPossible(heure_possible, duree);
			/*
			 * MAJ : On remplace l'ancienne liste des creneaux possibles du jour demande, par celle MAJ plus haut
			 */
			this.listeJours.put(dateDemandee, listeHoraireJourDispo);
		} else {
			trouver = false;
			System.out.println("Pas d'horaire disponible pour cette date");
		}
		/* Renvoie si on a trouver une plage horaire, ou non */
		return trouver;
	}
	
	private boolean creerPremiereReservationDate(Date dateDemandee, int heureDebutCours, int duree){
		/*
		 * Attribut listePlageHoraireDispo : On cree la liste des horaires disponibles pour cette journee.
		 */
		JourHoraireType listeHoraireJourDispo = new JourHoraireType();
		/* 
		 * On cree la nouvelle plage horaire, avec les informations prises precedemment
		 * On commence la reservation a 8h.
		 */
		Date date_fin = trouveDateFin(dateDemandee, duree);
		PlageHoraire plageHoraire = new PlageHoraire(this.numeroCreneau++, dateDemandee, date_fin);
		/*
		 * MAJ : On ajoute cette plage horaire dans la liste de nos plages horaires creees
		 */
		this.listePlagesHoraires.put(this.numeroCreneau, plageHoraire);
		/*
		 * MAJ : On enleve les creneaux possibles pour cette journee
		 */
		listeHoraireJourDispo.enleveCreneauPossible(heureDebutCours, duree);
		/*
		 * MAJ : On ajoute a la liste des jours, ce nouveau jour avec ses disponibilites de plages horaires
		 */
		this.listeJours.put(dateDemandee, listeHoraireJourDispo);
		return true;
	}
	
	private int trouveHorairePossibleJour(Date limite, int duree) {
		
		/*
		 * Attribut dateDemandee : simple formalite, on restructure la date
		 * pour etre certain qu'elle soit conforme a nos normes
		 */
		@SuppressWarnings("deprecation")
		Date dateDemandee = new Date(limite.getYear(), limite.getMonth(), limite.getDate(), limite.getHours(),limite.getMinutes());
		
		/*
		 * Attribut trouver : tant qu'une date n'est pas trouver, trouver sera a 'false' 
		 */
		boolean trouver = false;
		
		/*
		 * Tant qu'on a pas trouver de date possible a partir de la date limite et de la duree.
		 */
		while(!trouver) {
			/*
			 * Test si la date demandee n'est pas un creneau non exploitable.
			 */
			dateDemandee = verifieJourInListeCNE(dateDemandee);
			
			/*
			 * Test si le premier jour trouve (possible) existe dans la base de donnee et si oui,
			 * on regarde les plages horaires possibles pour cette date. Sinon on cree une nouvelle
			 * reservation pour cette journee. 
			 */
			if( listeJours.contains(dateDemandee) ){	
				/*
				 * Si une plage horaire est libre (pour le jour et sa duree demandee) alors on la trouve,
				 * et on la propose.
				 */
				trouver = donneHoraire(dateDemandee, duree);
			} else {
				/*
				 * Creation de la premiere reservation pour cette journee demandee, on decide d'ajouter cette
				 * premiere reservation a 8h. 
				 */
				trouver = creerPremiereReservationDate(dateDemandee, 8, duree);
			}
		}
		return this.numeroCreneau;
	}
}
